package pl.poznan.put.cs.to.tsp.sorted;

import java.util.LinkedList;

import pl.poznan.put.cs.to.tsp.common.IResultHolder;
import pl.poznan.put.cs.to.tsp.common.Vertexes;

public class SortedResult implements IResultHolder {
	private LinkedList<Integer> listA;
	private LinkedList<Integer> listB;
	private Vertexes vertexes;
	private int hashCode;

	public SortedResult(IResultHolder result) {
		this.vertexes = result.getVertexes();
		if (has0(result.getListA())) {
			this.listA = sortPhaseTwo(sortPhaseOne(result.getListA()));
			this.listB = sortPhaseTwo(sortPhaseOne(result.getListB()));
		} else {
			this.listB = sortPhaseTwo(sortPhaseOne(result.getListA()));
			this.listA = sortPhaseTwo(sortPhaseOne(result.getListB()));
		}
		this.hashCode = calculateHashCode();
	}

	private LinkedList<Integer> sortPhaseTwo(LinkedList<Integer> list) {
		if (list.size() < 3) {
			return list;
		} else if (list.get(1) < list.getLast()) {
			return list;
		} else {
			LinkedList<Integer> result = new LinkedList<Integer>();
			result.add(list.remove());
			while (!list.isEmpty()) {
				result.add(list.removeLast());
			}
			return result;
		}
	}

	private LinkedList<Integer> sortPhaseOne(LinkedList<Integer> linkedList) {
		int positionOfMin = getPositionOfMin(linkedList);
		LinkedList<Integer> result = new LinkedList<Integer>();
		result.addAll(linkedList.subList(positionOfMin, linkedList.size()));
		result.addAll(linkedList.subList(0, positionOfMin));
		return result;
	}

	private int getPositionOfMin(LinkedList<Integer> linkedList) {
		int min = findMin(linkedList);
		return linkedList.indexOf(min);
	}

	private int findMin(LinkedList<Integer> linkedList) {
		Integer min = Integer.MAX_VALUE;
		for (Integer i : linkedList) {
			if (i < min) {
				min = i;
			}
		}
		return min;
	}

	private boolean has0(LinkedList<Integer> list) {
		for (Integer i : list) {
			if (i.intValue() == 0) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int getLengthA() {
		return calculateLength(this.listA);
	}

	@Override
	public int getLengthB() {
		return calculateLength(this.listB);
	}

	@Override
	public LinkedList<Integer> getListA() {
		return this.listA;
	}

	@Override
	public LinkedList<Integer> getListB() {
		return this.listB;
	}

	@Override
	public Vertexes getVertexes() {
		return this.vertexes;
	}

	private int calculateLength(LinkedList<Integer> res) {
		int distance = 0;
		int first = res.getFirst();
		int last = res.getLast();
		int before = -1;

		for (Integer i : res) {
			if (before == -1) {
				distance += vertexes.getDistanceBetween(first, last);
			} else {
				distance += vertexes.getDistanceBetween(before, i);
			}
			before = i;
		}

		return distance;
	}

	@Override
	public int hashCode() {
		return this.hashCode;
	}

	public int calculateHashCode() {
		int prime = 7;
		int result = 0;
		for (int i : this.getListA()) {
			result = result * prime + i;
		}
		result = result * prime;
		for (int i : this.getListB()) {
			result = result * prime + i;
		}
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		final IResultHolder other = (IResultHolder) obj;
		if (this.listA.size() != other.getListA().size()) {
			return false;
		}
		if (this.listB.size() != other.getListB().size()) {
			return false;
		}
		LinkedList<Integer> otherListA = other.getListA();
		for (int i = 0; i < this.listA.size(); i++) {
			if (!this.listA.get(i).equals(otherListA.get(i))) {
				return false;
			}
		}
		LinkedList<Integer> otherListB = other.getListB();
		for (int i = 0; i < this.listB.size(); i++) {
			if (!this.listB.get(i).equals(otherListB.get(i))) {
				return false;
			}
		}
		return true;
	}

	@Override
	public int compareTo(IResultHolder o) {
		int local = this.getLengthA() + this.getLengthB();
		int other = o.getLengthA() + o.getLengthB();
		return local - other;
	}

	@Override
	public String toString() {
		return String.valueOf(this.getLengthA() + this.getLengthB());
	}
}
